#include "precomp.h"

namespace Tmpl {

// ===========================================================
// General stuff
// ===========================================================

WidgetStyle sStyleZero =	{0, 0, 0, 0, 0, 0};

WidgetStyle StyleStandard =	{0xFF666666, 0xFF222222, 0xFF888888, 0xFFAAAAAA, 0xFFFFFFFF, 0xFFAAAAAA};
WidgetStyle StyleBrown =	{0xFFD0EBED, 0xFF89AAAC, 0xFF557777, 0xFFE4F8F9, 0xFF000000, 0xFF776677};
WidgetStyle StyleWhite =	{0xFFEEEEEE, 0xFFCCCCCC, 0xFF888888, 0xFFFFFFFF, 0xFF000000, 0xFF777777};

WidgetStyle Widget::myDefaultStyle = StyleStandard;

// ===========================================================
// Widget implementation
// ===========================================================

Widget::Widget(const char* label, int w, int h, int x, int y) : 
	myX(x), myY(y), myW(w), myH(h), myIsLocked(false), myIsChanged(false)
{
	if(!memcmp(&myDefaultStyle, &sStyleZero, sizeof(WidgetStyle))) myDefaultStyle = StyleStandard;
	myLabel = DuplicateString(label);
	myStyle = myDefaultStyle;
}

void Widget::SetLabel(const char* label)
{
	if(myLabel) delete myLabel;
	myLabel = DuplicateString(label);
}

void Widget::SetLocked(bool locked)					{myIsLocked = locked;}
void Widget::SetPos(int x, int y)					{myX = x, myY = y;}
void Widget::SetSize(int w, int h)					{myW = w, myH = h;}

void Widget::SetStyle(WidgetStyle style)			{myStyle = style;}
void Widget::SetDefaultStyle(WidgetStyle style)		{myDefaultStyle = style;}

WidgetStyle Widget::GetDefaultStyle()				{return myDefaultStyle;}
Vec2i Widget::GetPos() const						{return Vec2i(myX, myY);}
Vec2i Widget::GetSize() const						{return Vec2i(myW, myH);}
const char* Widget::GetLabel() const				{return myLabel;}
bool Widget::IsLocked() const						{return myIsLocked;}
bool Widget::IsChanged() const						{return myIsChanged;}

// ===========================================================
// Text implementation
// ===========================================================

StaticText::StaticText(const char* label, int w, int h, int x, int y) : 
	Widget(label, w, h, x, y), myHalign(Font::LEFT), myValign(Font::TOP), 
	mColor(0xFFFFFFFF), mFont(&TmplFont)
{
}

void StaticText::SetAttribs(Font* font, Pixel color, Font::TextAlign halign, Font::TextAlign valign)
{
	mFont = font;
	myHalign = halign;
	myValign = valign;
	mColor = color;
}

void StaticText::Draw()
{
	int textX = myX, textY = myY;

	if(myHalign == Font::CENTER) textX = myX+myW/2;
	else if(myHalign == Font::RIGHT) textX = myX+myW;
	
	if(myValign == Font::CENTER) textY = myY+myH/2;
	else if(myValign == Font::BOTTOM) textY = myY+myH;

	TmplFont.Print(myLabel, textX, myY+myH/2, mColor, myHalign, myValign);
}

// ===========================================================
// Button implementation
// ===========================================================

Button::Button(const char* label, int w, int h, int x, int y) : 
	Widget(label, w, h, x, y), myIsClicked(false), myIsDown(false), myIsMouseOver(false)
{
}

void Button::Tick(float deltaTime)
{
	myIsMouseOver = mouse.IsInRect(myX, myY, myW, myH);
	myIsClicked = myIsChanged = myIsMouseOver && mouse.IsClicked(Mouse::LMB) && !myIsLocked;
	myIsDown = (myIsDown || myIsClicked) && (mouse.IsDown(Mouse::LMB) && !mouse.IsReleased(Mouse::LMB));
}

void Button::Draw()
{
	Draw::BarV(myX, myY, myX+myW, myY+myH, myIsDown ? myStyle.cBtm : myStyle.cTop, myIsDown ? myStyle.cTop : myStyle.cBtm);
	Draw::Box(myX, myY, myX+myW, myY+myH, myStyle.cBorder);
	TmplFont.Print(myLabel, myX+myW/2, myY+myH/2, myStyle.cText, Font::CENTER, Font::CENTER);
	if(myIsMouseOver) Draw::Box(myX+1, myY+1, myX+myW-1, myY+myH-1, myStyle.cHighLight);
}

// ===========================================================
// TextBox helper functions
// ===========================================================

#define ENABLE_CLIPBOARD	1
#if (ENABLE_CLIPBOARD)

#define WIN32_LEAN_AND_MEAN
#include "windows.h"

void ClipboardGet(char* buffer, int buffersize)
{
	buffer[0] = 0;
	if(!OpenClipboard(NULL)) return;
	HANDLE hData = GetClipboardData(CF_TEXT);
	char* source = (char*)GlobalLock(hData);
	if(source != NULL)
	{
		const int srclen = (int)strlen(source);
		const int cpylen = Min(srclen+1, buffersize);
		memcpy(buffer, source, cpylen);
		buffer[buffersize-1] = 0;
	}
	GlobalUnlock(hData);
	CloseClipboard();
}

void ClipboardSet(const char* string)
{
	if(!OpenClipboard(NULL)) return;

	EmptyClipboard();
	const int len = (int)strlen(string) + 1;
	HGLOBAL clipbuffer = GlobalAlloc(GMEM_DDESHARE, len);
	char* buffer = (char*)GlobalLock(clipbuffer);
	if(buffer != NULL)
	{
		memcpy(buffer, string, len);
		SetClipboardData(CF_TEXT,clipbuffer);
	}
	GlobalUnlock(clipbuffer);
	CloseClipboard();
}

#else /*ENABLE_CLIPBOARD*/

void ClipboardGet(char* buffer, int buffersize) {buffer[0] = 0;}
void ClipboardSet(const char* string){}

#endif /*ENABLE_CLIPBOARD*/

static const char sCaseL[] = {'`','1','2','3','4','5','6','7','8','9','0','-','=','[',']',';','\'','\\',',','.','/'};
static const char sCaseU[] = {'~','!','@','#','$','%','^','&','*','(',')','_','+','{','}',':', '"', '|','<','>','?'};
static const int sCaseSize = sizeof(sCaseL);

static char GetKeyboardChar()
{
	int symbol = 0;
	for(int i='a'; i<='z'; i++)		if(key.IsPressed(i)) symbol = i;
	for(int i='0'; i<='9'; i++)		if(key.IsPressed(i)) symbol = i;

	if(key.IsPressed(TK_DASH		)) symbol = '-';
	if(key.IsPressed(TK_COMMA		)) symbol = ',';
	if(key.IsPressed(TK_EQUAL		)) symbol = '=';
	if(key.IsPressed(TK_PERIOD		)) symbol = '.';
	if(key.IsPressed(TK_SPACE		)) symbol = ' ';

	if(key.IsPressed(TK_MULTIPLY	)) symbol = '*';
	if(key.IsPressed(TK_DIVIDE		)) symbol = '/';
	if(key.IsPressed(TK_ADD			)) symbol = '+';
	if(key.IsPressed(TK_SUBTRACT	)) symbol = '-';
	if(key.IsPressed(TK_PERIOD		)) symbol = '.';

	if(key.IsPressed(TK_BRACKET_L	)) symbol = '[';
	if(key.IsPressed(TK_BRACKET_R	)) symbol = ']';
	if(key.IsPressed(TK_SEMICOLON	)) symbol = ';';
	if(key.IsPressed(TK_QUOTE		)) symbol = '\'';
	if(key.IsPressed(TK_BACKSLASH	)) symbol = '\\';
	if(key.IsPressed(TK_SLASH		)) symbol = '/';

	if(key.IsDown(TK_SHIFT))
	{
		if(symbol >='a' && symbol <= 'z') symbol += int('A')-int('a');
		for(int i=0; i<sCaseSize; i++) if(symbol == sCaseL[i]) symbol = sCaseU[i];
	}
	return symbol;
}

static void DeleteStr(char* content, int pos, int len)
{
	const int strLen = (int)strlen(content);
	if(pos+len > strLen) len = strLen-pos;
	char* buffer = new char[strLen+1];
	strcpy_s(buffer, strLen+1, &content[pos+len]);
	strcpy_s(&content[pos], strLen-pos, buffer);
	delete[] buffer;
}

static void InsertStr(char* content, const char* string, int pos, int charlim)
{
	if(pos < 0) pos = 0; 
	if(pos > charlim) pos = charlim;
	const int tlen = (int)strlen(content);
	const int slen = (int)strlen(string), rlen = tlen-pos;
	char* buffer = new char[tlen + slen + 1];

	if(pos > 0) memcpy(buffer, content, pos);
	memcpy(&buffer[pos], string, slen);
	if(rlen > 0) memcpy(&buffer[pos+slen], &content[pos], rlen);
	int finalLen = Min(tlen + slen, charlim);
	memcpy(content, buffer, finalLen);
	content[finalLen] = 0;

	delete[] buffer;
}

static void InsertChar(char* content, char symbol, int pos, int charlim)
{
	char str[2] = {0}; str[0] = symbol;
	InsertStr(content, str, pos, charlim);
}

void TextBox::_ChangeCursorPos(int newPos, bool resetMarker)
{
	myCursorPos = newPos;
	if(resetMarker) myMarkerPos = myCursorPos;
	myFlickerTimer = 0.0f;
	_ClipCursorPos();
}

void TextBox::_ClipCursorPos()
{
	const int maxPos = (int)strlen(myContent);
	myCursorPos = Clamp(0, maxPos, myCursorPos);
	myMarkerPos = Clamp(0, maxPos, myMarkerPos);
}

void TextBox::_DeleteMarked()
{
	if(myCursorPos == myMarkerPos) return;
	int lpos = myCursorPos, rpos = myMarkerPos;
	if(lpos > rpos) {lpos = rpos, rpos = myCursorPos;}
	DeleteStr(myContent, lpos, rpos-lpos);
	_ChangeCursorPos(lpos, true);
}

// ===========================================================
// TextBox implementation
// ===========================================================

TextBox::TextBox(const char* label, int w, int h, int x, int y, int maxChars, int textBoxH) : 
	Widget(label, w, h, x, y), myMaxChars(maxChars), myTextBoxH(textBoxH), myIsSelected(false),
	myMarkerPos(0), myPixelsOffset(0), myCursorPos(0), myFlickerTimer(0.0f), myIsMouseOver(false)
{
	myContent = new char[maxChars+1];
	memset(myContent, 0, maxChars+1);
}

void TextBox::Tick(float deltaTime)
{
	myIsChanged = false;
	myFlickerTimer += deltaTime;

	const int mouseX = mouse.GetX(), mouseY = mouse.GetY();
	
	const int boxX = myX + TmplFont.GetWidth(myLabel);
	const int boxY = myY + myH/2 - myTextBoxH/2;
	const int boxW = myW - (boxX-myX);
	
	myIsMouseOver = mouse.IsInRect(boxX, boxY, boxW, myTextBoxH);
	const bool shiftDown = key.IsDown(TK_SHIFT);
	if(mouse.IsClicked(Mouse::LMB)) myIsSelected = myIsMouseOver;
	
	if(!myIsSelected) return;

	// constrain text cursor position to the textbox
	float cursorPos = myPixelsOffset + (float)TmplFont.GetWidth(myContent, 0, myCursorPos);
	if(cursorPos < 0)		myPixelsOffset += Min(-cursorPos, deltaTime * 256.0f);
	if(cursorPos > boxW-4)	myPixelsOffset -= Min(cursorPos - (boxW-4), deltaTime * 256.0f);

	// Finalize input when return or left mouse button is pressed
	if((mouse.IsClicked(Mouse::LMB) && !myIsMouseOver) || key.IsPressed(TK_RETURN) || myIsLocked)
	{ 
		_ChangeCursorPos(0, true);
		myIsSelected = false;
		return; 
	}
	
	// Handle mouse controls
	if(mouse.IsClicked(Mouse::LMB) || mouse.IsDown(Mouse::LMB))
	{
		const bool reset = mouse.IsClicked(Mouse::LMB) && !key.IsDown(TK_SHIFT);
		const int newPos = TmplFont.GetCharPos(myContent, mouseX-(boxX+2)-(int)myPixelsOffset);
		_ChangeCursorPos(newPos, reset);
	}

	// Handle arrow key presses
	if(key.IsPressed(TK_LEFT))		_ChangeCursorPos(myCursorPos-1, !shiftDown);
	if(key.IsPressed(TK_RIGHT))	_ChangeCursorPos(myCursorPos+1, !shiftDown);

	// Handle deletion of characters
	if(myCursorPos != myMarkerPos)
	{
		// Deletion of marked text
		if(key.IsPressed(TK_DELETE) || key.IsPressed(TK_BACKSPACE))
		{
			_DeleteMarked();
			myIsChanged = true;
		}
	}
	else
	{
		// Deletion of single character
		if(key.IsPressed(TK_DELETE)) DeleteStr(myContent, myCursorPos, 1);
		if(key.IsPressed(TK_BACKSPACE))
		{ 
			DeleteStr(myContent, myCursorPos-1, 1);
			_ChangeCursorPos(myCursorPos-1,true);
			myIsChanged = true;
		}
	}

	// Handle character keys
	if(key.IsDown(TK_CTRL))
	{
		// Ctrl + [] shortcuts
		if(key.IsPressed('a'))
		{
			myMarkerPos = 0; 
			myCursorPos = (int)strlen(myContent);
			_ClipCursorPos();
		}
		if(key.IsPressed('v'))
		{
			char buf[512] = {0};
			ClipboardGet(buf, 512);
			if(buf[0] != 0)
			{
				_DeleteMarked();
				InsertStr(myContent, buf, myCursorPos, myMaxChars);
				_ChangeCursorPos(myCursorPos + (int)strlen(buf), true);
				myIsChanged = true;
			}
		}
		if(key.IsPressed('c') || key.IsPressed('x'))
		{
			int start = 0, end = (int)strlen(myContent);
			if(myCursorPos != myMarkerPos)
			{
				start = Min(myCursorPos, myMarkerPos);
				end = Max(myCursorPos, myMarkerPos);
			}
			const int len = end - start;
			char* temp = new char[len+1];
			memcpy(temp, myContent + start, len); 
			temp[len] = 0;
			ClipboardSet(temp);
			if(key.IsPressed('x')) _DeleteMarked();
			delete[] temp;
			myIsChanged = true;
		}
	}
	else
	{
		// Regular typing
		int keypress = GetKeyboardChar();
		if(keypress)
		{
			_DeleteMarked();
			InsertChar(myContent, (char)keypress, myCursorPos, myMaxChars);
			_ChangeCursorPos(myCursorPos+1, true);
			myIsChanged = true;
		}
	}
}

void TextBox::Draw()
{
	TmplFont.Print(myLabel, myX, myY+myH/2, myStyle.cText, Font::LEFT, Font::CENTER);

	const int boxX = myX + TmplFont.GetWidth(myLabel);
	const int boxY = myY + myH/2 - myTextBoxH/2;
	const int boxW = myW - (boxX-myX), boxH = myTextBoxH;

	Draw::BarV(boxX, boxY, boxX+boxW, boxY+myTextBoxH, myStyle.cBtm, myStyle.cTop);
	Draw::Box(boxX, boxY, boxX+boxW, boxY+myTextBoxH, myStyle.cBorder);

	Draw::ClipRectPush(boxX, boxY, boxX+boxW, boxY+boxH);
	TmplFont.Print(myContent, boxX+2+(int)myPixelsOffset, boxY+boxH/2-TmplFont.GetHeight()/2, myStyle.cText);
	if(myIsSelected)
	{
		int cursorX = boxX+2 + TmplFont.GetWidth(myContent, 0, myCursorPos) + (int)myPixelsOffset;
		int markerX = boxX+2 + TmplFont.GetWidth(myContent, 0, myMarkerPos) + (int)myPixelsOffset;
		cursorX = Clamp(boxX, boxX+boxW, cursorX);
		markerX = Clamp(boxX, boxX+boxW, markerX);

		while(myFlickerTimer > 1.0f) myFlickerTimer -= 1.0f;
		if(myFlickerTimer < 0.5f)
		{
			Draw::LineV(cursorX, boxY+1, boxY+boxH-2, myStyle.cText);
		}
		if(myCursorPos != myMarkerPos)
		{
			const int minX = Min(cursorX, markerX), maxX = Max(cursorX, markerX);
			Draw::SetBlendMode(Draw::INVERT);
			Draw::Bar(minX, boxY+1, maxX, boxY+boxH-1, 0xFFFFFFFF);
			Draw::SetBlendMode();
		}
	}
	Draw::ClipRectPop();
}

void TextBox::SetSelected(bool selected)
{
	myIsSelected = selected;
}

void TextBox::SetContent(const char* content)
{
	const int len = Min((int)strlen(content)+1, myMaxChars+1);
	memcpy(myContent, content, len);
	myContent[myMaxChars] = 0;
}

// ===========================================================
// Slider implementation
// ===========================================================

#define SLIDERBOX_H	8
#define	SLIDERBOX_W	8

void Slider::SetValue(double value)
{
	myValue = Clamp(myMinValue, myMaxValue, value);
}

void Slider::SetRange(double min, double max)
{
	myMinValue = min, myMaxValue = max;
	myValue = Clamp(myMinValue, myMaxValue, myValue);
}

Slider::Slider(const char* label, int w, int h, int x, int y, bool compact, int decimals) : 
	Widget(label, w, h, x, y), myDecimalPlaces(decimals), myMinValue(0), myMaxValue(1), myValue(0.5),
	myIsSelected(false), myIsMouseOver(false), myIsCompact(compact)
{
}

void Slider::Tick(float deltaTime)
{
	myIsChanged = false;

	const int mouseX = mouse.GetX(), mouseY = mouse.GetY();
	
	char format[64], minTxt[64], maxTxt[64];
	sprintf_s(format, "%%.%if", myDecimalPlaces);
	sprintf_s(minTxt, format, myMinValue);
	sprintf_s(maxTxt, format, myMaxValue);

	int minTxtW = TmplFont.GetWidth(minTxt);
	int maxTxtW = TmplFont.GetWidth(maxTxt);
	int printW = (int)Max(minTxtW, maxTxtW);

	const int lineX = myX + TmplFont.GetWidth(myLabel);
	const int lineW = myW - (lineX-myX) - 6 - (myIsCompact ? printW : 0);
	const int lineY = myY + myH/2 - (myIsCompact ? 0 : 5);

	myIsMouseOver = mouse.IsInRect(lineX-SLIDERBOX_W/2, lineY-SLIDERBOX_H/2-4, lineW+SLIDERBOX_W, SLIDERBOX_H+8);
	if(mouse.IsClicked(Mouse::LMB)) myIsSelected = myIsMouseOver;
	myIsSelected = myIsSelected && mouse.IsDown(Mouse::LMB) && !myIsLocked;

	if(myIsSelected)
	{
		double oldVal = myValue;
		double minX = (double)lineX, maxX = (double)(lineX + lineW);
		double out = InvLerp(minX, maxX, (double)mouse.GetX());
		myValue = Clamp(myMinValue, myMaxValue, Lerp(myMinValue, myMaxValue, out));
		if(myValue != oldVal) myIsChanged = true;
	}
}

void Slider::Draw()
{
	char format[64], minTxt[64], maxTxt[64], valTxt[64];
	sprintf_s(format, "%%.%if", myDecimalPlaces);
	sprintf_s(minTxt, format, myMinValue);
	sprintf_s(maxTxt, format, myMaxValue);
	sprintf_s(valTxt, format, myValue);

	int minTxtW = TmplFont.GetWidth(minTxt);
	int maxTxtW = TmplFont.GetWidth(maxTxt);
	int printW = (int)Max(minTxtW, maxTxtW);

	const int lineX = myX + TmplFont.GetWidth(myLabel) + SLIDERBOX_W/2;
	const int lineW = myW - (lineX-myX) - SLIDERBOX_W/2 - (myIsCompact ? printW : 0);
	const int lineY = myY + myH/2 - (myIsCompact ? 0 : 5);
	const int textY = myY + myH/2 + (myIsCompact ? 0 : 5);

	Draw::LineH(lineX, lineY, lineX+lineW, myStyle.cBtm);
	Draw::LineH(lineX, lineY+1, lineX+lineW, myStyle.cTop);

	TmplFont.Print(myLabel, myX, lineY-TmplFont.GetHeight()/2, myStyle.cText);
	
	const double pct = InvLerp(myMinValue, myMaxValue, myValue);
	const int x = lineX + (int)(pct * (double)lineW);
	const int wh = SLIDERBOX_W/2, hh = SLIDERBOX_H/2;
	
	Draw::BarV(x-wh, lineY-hh, x+wh, lineY+hh, myStyle.cHighLight, myStyle.cTop);
	Draw::Box(x-wh, lineY-hh, x+wh, lineY+hh, myStyle.cBorder);

	if(myIsCompact)
	{
		TmplFont.Print(valTxt, myX+myW, lineY-TmplFont.GetHeight()/2, myStyle.cText, Font::RIGHT);
	}
	else
	{
		TmplFont.Print(minTxt, lineX, textY-TmplFont.GetHeight()/2, myStyle.cText, Font::LEFT);
		TmplFont.Print(valTxt, lineX+lineW/2, textY-TmplFont.GetHeight()/2, myStyle.cText, Font::CENTER);
		TmplFont.Print(maxTxt, lineX+lineW, textY-TmplFont.GetHeight()/2, myStyle.cText, Font::RIGHT);
	}
}

// ===========================================================
// CheckBox implementation
// ===========================================================

CheckBox::CheckBox(const char* label, int w, int h, int x, int y, bool checked, int boxSize) : 
	Widget(label, w, h, x, y), myIsChecked(checked), myIsMouseOver(false), myBoxSize(boxSize)
{
}

void CheckBox::Tick(float deltaTime)
{
	myIsChanged = false;
	const int boxY = myY+myH/2-myBoxSize/2;
	myIsMouseOver = mouse.IsInRect(myX, boxY, myBoxSize, myBoxSize) && !myIsLocked;
	if(mouse.IsClicked(Mouse::LMB) && myIsMouseOver)
	{
		myIsChanged = true;
		myIsChecked = !myIsChecked;
	}
}

void CheckBox::Draw()
{
	const int boxY = myY+myH/2-myBoxSize/2;
	Draw::BarV(myX, boxY, myX+myBoxSize, boxY+myBoxSize, myStyle.cBtm, myStyle.cTop);
	Draw::Box(myX, boxY, myX+myBoxSize, boxY+myBoxSize, myStyle.cBorder);

	if(myIsChecked) Draw::BarV(myX+2, boxY+2, myX+myBoxSize-2, boxY+myBoxSize-2, myStyle.cHighLight, myStyle.cTop);

	TmplFont.Print(myLabel, myX+myBoxSize+4, myY+myH/2-TmplFont.GetHeight()/2, myStyle.cText);
}

// ===========================================================
// DropList implementation
// ===========================================================

ListItem::ListItem(const char* label)
{
	myName = DuplicateString(label);
}

void ListItem::Draw(int w, int h, int x, int y, const WidgetStyle& style, bool selected)
{
	TmplFont.Print(myName, x+3, y+h/2-TmplFont.GetHeight()/2+1, selected ? style.cText : style.cTextDark);
}

struct DropListDataImp
{
	std::vector<ListItem*> list;
};

DropList::DropList(const char* label, int w, int h, int x, int y, int itemyH) :
	Widget(label, w, h, x, y), myIsDown(false), myIsMouseOver(false),	myIsSelected(0), 
	myIsMouseOverItem(0), myItemH(itemyH)
{
	myData = new DropListDataImp;
}

DropList::~DropList()
{
	const int size = (int)myData->list.size();
	for(int i=0; i<size; ++i) delete myData->list[i];
	delete myData;
}

void DropList::Clear()
{
	const int size = (int)myData->list.size();
	for(int i=0; i<size; ++i) delete myData->list[i];
	myData->list.clear();
	SetSelected(0);
}

void DropList::AddItem(const char* name)
{
	myData->list.push_back(new ListItem(name));
}

void DropList::AddItemCustom(ListItem* item)
{
	myData->list.push_back(item);
}

void DropList::SetSelected(int index)
{
	myIsSelected = Clamp(0, (int)myData->list.size(), index);
}

void DropList::Tick(float deltaTime)
{
	myIsChanged = false;

	const int boxX = myX + TmplFont.GetWidth(myLabel);
	const int boxW = myW - (boxX-myX), boxH = myItemH;
	const int boxY = myY + myH/2 - myItemH/2;

	myIsMouseOver = mouse.IsInRect(boxX, boxY, boxW, boxH) && !myIsLocked;

	const int mouseX = mouse.GetX(), mouseY = mouse.GetY();
	if(mouse.IsClicked(Mouse::LMB)) myIsDown = myIsMouseOver;

	const int listX = myX + TmplFont.GetWidth(myLabel);
	const int listW = myW - (listX-myX), listH = myItemH;
	const int listY = myY + myH/2 - myItemH/2 + listH;

	if(myIsDown)
	{
		const int size = (int)myData->list.size();
		for(int i=0; i<size; ++i)
		{
			bool mouseOverList = mouse.IsInRect(listX, listY+i*listH, listW, listH);
			if(mouseOverList && mouse.IsReleased(Mouse::LMB))
			{
				if(i != myIsSelected) myIsChanged = true;
				myIsSelected = i;
			}
			if(mouseOverList) myIsMouseOverItem = i;
		}
	}
	else myIsMouseOverItem = myIsSelected;
	myIsDown = myIsDown && mouse.IsDown(Mouse::LMB);
}

void DropList::Draw()
{
	TmplFont.Print(myLabel, myX, myY+myH/2-TmplFont.GetHeight()/2, myStyle.cText);

	const int boxX = myX + TmplFont.GetWidth(myLabel);
	const int boxW = myW - (boxX-myX), boxH = myItemH;
	const int boxY = myY + myH/2 - myItemH/2;

	Draw::BarV(boxX, boxY, boxX+boxW, boxY+boxH, myStyle.cTop, myStyle.cBtm);
	if(!myIsLocked) Draw::BarV(boxX+boxW-boxH, boxY, boxX+boxW, boxY+boxH, myStyle.cHighLight, myStyle.cTop);
	Draw::Box(boxX, boxY, boxX+boxW, boxY+boxH, myStyle.cBorder);

	if(!myIsLocked)
	{
		const int arrowX = boxX+boxW-boxH/2;
		for(int i=0; i<3; ++i)
		{
			const int y = boxY+boxH/2-3+i;
			const int w = Max(0, 3-i);
			Draw::LineH(arrowX-w, y, arrowX+w-1, myStyle.cBtm);
		}
	}
	if(myIsSelected >= 0 && myIsSelected < (int)myData->list.size())
	{
		Draw::ClipRectPush(boxX, boxY, boxX+boxW, boxY+boxH);
		myData->list.at(myIsSelected)->Draw(boxW, boxH, boxX, boxY, myStyle, true);
		Draw::ClipRectPop();
	}

	// Draw list
	const int size = (int)myData->list.size();
	if(size == 0 || !myIsDown) return;
	Draw::DepthPush(1);

	const int listX = myX + TmplFont.GetWidth(myLabel);
	const int listW = myW - (listX-myX), listH = myItemH;
	const int listY = myY + myH/2 - myItemH/2 + listH-1;

	Draw::BarV(listX, listY, listX+listW, listY+size*listH, myStyle.cBtm, myStyle.cTop);
	Draw::LineV(listX+1, listY, listY+size*listH-1, myStyle.cBorder);
	Draw::LineV(listX+listW, listY, listY+size*listH-1, myStyle.cBorder);
	Draw::LineH(listX, listY+size*listH-1, listX+listW-2, myStyle.cBorder);
	
	Draw::ClipRectPush(listX, listY, listX+listW, listY+size*listH);
	for(int i=0; i<size; ++i)
	{ 
		const int y = listY+i*listH;
		ListItem* item = myData->list.at(i);
		item->Draw(listW, listH, listX, y, myStyle, (myIsSelected == i));
		
		if(myIsMouseOverItem == i)
		{
			Draw::SetBlendMode(Draw::ADD);
			Draw::Bar(listX+1, y+1, listX+listW-1, y+listH-1, 0xFF222222);
			Draw::SetBlendMode();
		}
	}
	Draw::ClipRectPop();
	Draw::DepthPop();
}

// ===========================================================
// SelectList implementation
// ===========================================================

struct SelectListDataImp
{
	std::vector<ListItem*> list;
};

SelectList::SelectList(const char* label, int w, int h, int x, int y, int itemyH) :
	Widget(label, w, h, x, y), myIsSelected(0), myIsMouseOverItem(0), myItemH(itemyH)
{
	myData = new SelectListDataImp;
}

SelectList::~SelectList()
{
	const int size = (int)myData->list.size();
	for(int i=0; i<size; ++i) delete myData->list[i];
	delete myData;
}

void SelectList::Clear()
{
	const int size = (int)myData->list.size();
	for(int i=0; i<size; ++i) delete myData->list[i];
	myData->list.clear();
	SetSelected(0);
}

void SelectList::AddItem(const char* label)
{
	myData->list.push_back(new ListItem(label));
}

void SelectList::AddItemCustom(ListItem* item)
{
	myData->list.push_back(item);
}

void SelectList::SetSelected(int index)
{
	myIsSelected = Clamp(0, (int)myData->list.size(), index);
}

void SelectList::Tick(float deltaTime)
{
	myIsChanged = false;

	const int mouseX = mouse.GetX(), mouseY = mouse.GetY();

	const int boxY = (myLabel[0] ? myY+TmplFont.GetHeight()+2 : myY);
	const int boxH = (myLabel[0] ? myH-TmplFont.GetHeight()-2 : myH);

	myIsMouseOverItem = -1;
	if(mouseX >= myX && mouseY >= boxY && mouseX <= myX+myW && mouseY <= boxY+boxH)
	{
		const int size = (int)myData->list.size();
		for(int i=0; i<size; ++i)
		{
			const int y = boxY+i*myItemH+1;
			if(mouseY >= y && mouseY <= y+myItemH)
			{
				myIsMouseOverItem = i;
				if(mouse.IsClicked(Mouse::LMB))
				{
					if(i != myIsSelected) myIsChanged = true;
					myIsSelected = i;
				}
			}
		}
	}
}

void SelectList::Draw()
{
	const int boxY = (myLabel[0] ? myY+TmplFont.GetHeight()+2 : myY);
	const int boxH = (myLabel[0] ? myH-TmplFont.GetHeight()-2 : myH);

	TmplFont.Print(myLabel, myX, myY+2, myStyle.cText);
	Draw::BarV(myX, boxY, myX+myW, boxY+boxH, myStyle.cBtm, myStyle.cTop);
	Draw::Box(myX, boxY, myX+myW, boxY+boxH, myStyle.cBorder);

	Draw::ClipRectPush(myX, boxY, myX+myW, boxY+boxH-1);
	const int size = (int)myData->list.size();
	for(int i=0; i<size; ++i)
	{
		const int y = boxY+i*myItemH+1;
		myData->list.at(i)->Draw(myW, myItemH, myX, y, myStyle, (myIsSelected == i));
		if(myIsMouseOverItem == i)
		{
			Draw::SetBlendMode(Draw::ADD);
			Draw::Bar(myX+1, y, myX+myW-1, y+myItemH, 0xFF222222);
			Draw::SetBlendMode(Draw::NORMAL);
		}
		if(myIsMouseOverItem == i || myIsSelected == i) Draw::SetBlendMode();
	}
	Draw::ClipRectPop();
}

// ===========================================================
// Scrollbar implementation
// ===========================================================

ScrollBar::ScrollBar(const char* label, int w, int h, int x, int y, bool horizontal, bool addButtons) :
	Widget(label, w, h, x, y), myScrollPos(0), myRealArea(1), myVisibleArea(1), myDragPos(0),
	myIsHorizontal(horizontal), myHasButtons(addButtons), myIsDragging(false), myIsMouseOver(false), myDragScroll(0)
{
	myScrollUpButton = myScrollDownButton = 0;
	if(myHasButtons)
	{
		const int size = (myIsHorizontal ? myH : myW);
		const int posX = (myIsHorizontal ? myX+myW-myH : myX);
		const int posY = (myIsHorizontal ? myY+myH-myW : myY);
		myScrollUpButton = new Button("-", size, size, myX, myY);
		myScrollDownButton = new Button("+", size, size, posX, posY);
	}
}

ScrollBar::~ScrollBar()
{
	if(myScrollUpButton) delete myScrollUpButton;
	if(myScrollDownButton) delete myScrollDownButton;
}

void ScrollBar::SetPos(int x, int y)
{
	Widget::SetPos(x, y);
	if(myHasButtons)
	{
		myScrollUpButton->SetPos(myX, myY);
		if(myIsHorizontal) myScrollDownButton->SetPos(myX+myW-myH, myY); else myScrollDownButton->SetPos(myX, myY+myH-myW);
	}
}

void ScrollBar::SetSize(int w, int h)
{
	Widget::SetSize(w, h);
	if(myHasButtons)
	{
		myScrollUpButton->SetPos(myX, myY);
		if(myIsHorizontal) myScrollDownButton->SetPos(myX+myW-myH, myY); else myScrollDownButton->SetPos(myX, myY+myH-myW);
	}
}

void ScrollBar::SetRange(int realArea, int visibleArea)
{
	myRealArea = realArea, myVisibleArea = visibleArea;
	if(myVisibleArea > myRealArea) myVisibleArea = myRealArea;
	if(myScrollPos > myRealArea - myVisibleArea) myScrollPos = myRealArea - myVisibleArea;
}

void ScrollBar::Tick(float deltaTime)
{
	myIsChanged = false;

	if(myHasButtons)
	{
		myScrollUpButton->Tick(deltaTime);
		myScrollDownButton->Tick(deltaTime);
	}

	if(myRealArea <= myVisibleArea) return;

	int mouseX = mouse.GetX(), mouseY = mouse.GetY();
	int relX = myX, relY = myY, relW = myW, relH = myH;
	if(!myIsHorizontal)
	{
		mouseX = mouse.GetY(), mouseY = mouse.GetX();
		relX = myY, relY = myX, relW = myH, relH = myW;
	}
	if(myHasButtons)
	{
		relX += relH, relW -= relH*2;
		if(myScrollUpButton->IsClicked())		myScrollPos -= myRealArea/8;
		if(myScrollDownButton->IsClicked())	myScrollPos += myRealArea/8;
	}

	myIsMouseOver = mouse.IsInRect(myX, myY, myW, myH);
	if(myIsMouseOver && mouse.IsClicked(Mouse::LMB))
	{
		double len = (double)relW;
		double scrollRange = (double)myRealArea;
		int start = (int)(len * (double)(myScrollPos) / scrollRange);
		int end = (int)(len * (double)(myScrollPos + myVisibleArea) / scrollRange);

		if(mouseX >= relX+start && mouseX <= relX+end)
		{
			myDragPos = mouseX;
			myDragScroll = myScrollPos;
			myIsDragging = true;
		}
		else if(mouseX >= relX && mouseX < relX+start) myScrollPos -= myRealArea/8;
		else if(mouseX <= relX+relW && mouseX > relX+end) myScrollPos += myRealArea/8;
	}
	if(myIsDragging)
	{
		const int oldScroll = myScrollPos;
		const double barDelta = (double)(mouseX - myDragPos) / (double)relW;
		myScrollPos = (int)((double)myDragScroll + (double)myRealArea * barDelta);
		if(myScrollPos != oldScroll) myIsChanged = true;
		if(!mouse.IsDown(Mouse::LMB)) myIsDragging = false;
	}
	myScrollPos = Clamp(0, myRealArea - myVisibleArea, myScrollPos);
}
void ScrollBar::Draw()
{
	const int ofs = (myHasButtons ? (myIsHorizontal ? myH : myW) : 0);
	const int ilen = (myIsHorizontal ? myW-ofs*2 : myH-ofs*2);

	if(myIsHorizontal)	Draw::BarV(myX+ofs, myY, myX+myW-ofs, myY+myH, myStyle.cBtm, myStyle.cTop);
	else Draw::BarH(myX, myY+ofs, myX+myW, myY+myH-ofs, myStyle.cBtm, myStyle.cTop);

	double len = (double)ilen;
	double scrollRange = (double)myRealArea;
	int start = ofs + (int)(len * (double)(myScrollPos) / scrollRange);
	int end = ofs + (int)(len * (double)(myScrollPos + myVisibleArea) / scrollRange);
	if(myRealArea <= myVisibleArea) start = 0, end = (myIsHorizontal ? myW : myH);

	if(myIsHorizontal)
	{
		Draw::BarV(myX+start, myY, myX+end, myY+myH, myStyle.cHighLight, myStyle.cTop);
		Draw::Box(myX+start, myY, myX+end, myY+myH, myStyle.cBorder);
	}
	else
	{
		Draw::BarH(myX, myY+start, myX+myW, myY+end, myStyle.cHighLight, myStyle.cTop);
		Draw::Box(myX, myY+start, myX+myW, myY+end, myStyle.cBorder);
	}

	if(myHasButtons)
	{
		myScrollUpButton->Draw();
		myScrollDownButton->Draw();
	}

	Draw::Box(myX, myY, myX+myW, myY+myH, myStyle.cBorder);
}

// ===========================================================
// TextLog implementation
// ===========================================================

#define SCROLLBAR_W	12

struct TextLogElement
{
	TextLogElement(const char* Tag, const char* Msg, Pixel Col) : tag(Tag), msg(Msg), color(Col){}
	std::string tag, msg;
	Pixel color;
};

struct TextLogDataImp
{
	std::deque<TextLogElement> log;
};

TextLog::TextLog(const char* label, int w, int h, int x, int y, int maxLines) :
	Widget(label, w, h, x, y), mScrollBar("", SCROLLBAR_W, h, x+w-SCROLLBAR_W, y), myMaxValueLines(maxLines)
{
	myData = new TextLogDataImp;
	SetSize(myW, myH);
}

TextLog::~TextLog()
{
	delete myData;
}

void TextLog::Clear()
{
	myData->log.clear();
}

void TextLog::AddLine(const char* tag, const char* message, Pixel tagColor)
{
	myData->log.push_back(TextLogElement(tag, message, tagColor));
	if((int)myData->log.size() > myMaxValueLines) myData->log.pop_front();
}

void TextLog::SetPos(int x, int y)
{
	Widget::SetPos(x, y);
	mScrollBar.SetPos(myX+myW-SCROLLBAR_W, y);
}

void TextLog::SetSize(int w, int h)
{
	Widget::SetSize(w, h);
	const int fieldY = (myLabel[0] ? myY+TmplFont.GetHeight()+2 : myY), fieldH = (myLabel[0] ? myH-TmplFont.GetHeight()-2 : myH);
	mScrollBar.SetSize(SCROLLBAR_W, fieldH);
	mScrollBar.SetPos(myX+myW-SCROLLBAR_W, fieldY);
}

void TextLog::Tick(float deltaTime)
{
	const int fieldH = (myLabel[0] ? myH-TmplFont.GetHeight()-2 : myH);
	int visibleLines = Max(1, (fieldH-4)/TmplFont.GetHeight()), totalLines = 0;
	for(unsigned int i=0; i<myData->log.size(); ++i)
	{
		const TextLogElement& item = myData->log[i];
		const int dx = TmplFont.GetWidth(item.tag.c_str());
		totalLines += TmplFont.GetLineCount(item.msg.c_str(), myW-SCROLLBAR_W-4-dx);
	}

	mScrollBar.SetRange(totalLines, visibleLines);
	mScrollBar.Tick(deltaTime);
}

void TextLog::Draw()
{
	const int y = (myLabel[0] ? myY+TmplFont.GetHeight()+2 : myY), h = (myLabel[0] ? myH-TmplFont.GetHeight()-2 : myH);

	TmplFont.Print(myLabel, myX, myY+2, myStyle.cText);
	Draw::BarV(myX, y, myX+myW, y+h, myStyle.cBtm, myStyle.cTop);
	Draw::Box(myX, y, myX+myW, y+h, myStyle.cBorder);

	Draw::ClipRectPush(myX+1, y+1, myX+myW-SCROLLBAR_W-1, y+h-1);
	int lineY = -mScrollBar.GetScrollPos(), size = (int)myData->log.size();
	for(int i=0; i<size; ++i)
	{
		const TextLogElement& item = myData->log[i];
		const int dx = TmplFont.GetWidth(item.tag.c_str());
		const int dy = y + lineY*TmplFont.GetHeight() + 2;
		lineY += TmplFont.PrintWrap(item.msg.c_str(), myX+2+dx, dy, myW-SCROLLBAR_W-4-dx, myStyle.cText);
		TmplFont.Print(item.tag.c_str(), myX+2, dy, item.color);
	}
	Draw::ClipRectPop();

	mScrollBar.Draw();
}

}; // namespace tmpl